package scutiger.util;

import com.esotericsoftware.reflectasm.ConstructorAccess;
import com.esotericsoftware.reflectasm.FieldAccess;
import com.esotericsoftware.reflectasm.MethodAccess;
import org.apache.log4j.Logger;
import scutiger.annotation.*;
import scutiger.impl.LBean;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.net.URL;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;


/**
 * 类加载器 @author lei
 */
public class LEIClassLoader {

    private static Logger logger = Logger.getLogger(LEIClassLoader.class.getName());

    /**
     * 装载的controller容器
     */
    private static Map<String, LBean> classControllers = new HashMap<>();

    /**Class文件一级缓存
     *
     */
    private static List<Class> classList = new ArrayList<>();

    /**Class文件  class类名  class具体信息
     *
     */
    private static Map<String,Class> classMap = new HashMap<>();

    /**一级缓存 缓存创建对象时的构造器
     *
     */
    public static final Map<String,ConstructorAccess> firstConstructs = new HashMap<>();

    /**二级缓存 缓存创建对象时的构造器
     *
     */
    public static final Map<String,ConstructorAccess> secondConstructs = new HashMap<>();

    /**类调用次数统计
     *
     */
    public final static LinkedHashMap<String,Integer> count = new LinkedHashMap<>();

    /**存放接口实现类
     *
     */
    private static Map<String,List<Class>> interfaceImplClassMap = new HashMap<>();

    /**用于存放当前线程的Map实例缓存中
     *
     */
    public final static ConcurrentHashMap<String,Map<String,Object>> currentThreadSingleBeans = new ConcurrentHashMap<>();

    private static String[] packages = null;
    /**映射方式 java/asm
     *
     */
    public static String reflect = null;
    public static final String JAVA = "java",ASM="asm";


    public static void init(){
        packages = packages==null?Objects.requireNonNull(YmlUtil.getYml("class.scan")).toString().split(","):packages;
        reflect = reflect==null?Objects.requireNonNull(YmlUtil.getYml("class.reflect")).toString():reflect;
        initAllClassByPackages(packages);
        initAllControllersByPackages(BaseController.class);
    }


    /**获取所有控制层Map
     *
     * @return
     */
    public static Map<String, LBean> getMapControllers() {
        return classControllers;
    }

    /**初始化所有class
     *
     * @param packages
     */
    private static void initAllClassByPackages(String[] packages){
        if(classList.isEmpty()){
            for (String aPackage : packages) {
                ArrayList<Class> allClass = getAllClass(aPackage);
                classList.addAll(allClass);
                for (Class aClass : allClass) {
                    classMap.put(aClass.getTypeName(),aClass);
                }
            }
            if(YmlUtil.log)logger.info("初始化完毕，总共加载class文件 "+classMap.size()+" 个");
        }
    }

    /**
     * 获取该接口所有的子类 @param clazz 需要获取子类的接口 @param packages 扫描的包路径 @return ArrayList
     */
    private static Map<String, LBean> initAllControllersByPackages(Class<BaseController> clazz) {/* 1. 判断是否是接口 2.装载指定包名下所有的类 3.判断是否实现了该接口 4.放到List里面*/
            for (Class cs : classList) {
                /*判断是否接口 &&clazz.isAssignableFrom(cs)*/
                logger.info("开始装载类:" + cs.getName());
                if (!cs.isInterface()&&clazz.isAssignableFrom(cs)) {
                        /*是否引用了注解LEIController*/
                        if (cs.isAnnotationPresent(LController.class)) {
                            /*是否引用了注解LoginCheck 登录校验*/
                            boolean loginCheck = cs.isAnnotationPresent(LoginCheck.class);
                            /*获取controller注解值*/
                            LController controller = (LController) cs.getAnnotation(LController.class);
                            String controllerName = controller.value();
                            if (StringUtils.isEmpty(controllerName)) controllerName = controller.name();
                            else if (StringUtils.isEmpty(controllerName)) continue;
                            controllerName = autoAddPrefix(controllerName);
                            Method[] methods = cs.getDeclaredMethods();
                            logger.info("类" + cs.getName() + " 的方法有" + methods.length + " 个");
                            HashMap<String, scutiger.impl.LMethod> leiMethodMap = parseMethodMap(methods);
                            HashMap<String, Integer> leiMethodMapIndex = parseMethodMapIndex(cs);
                            LBean bean = LBean.builder()
                                    .aClass(cs)
                                    .LEIMethodMap(leiMethodMap)
                                    .LEIMethodMapIndex(leiMethodMapIndex)
                                    .name(controllerName)
                                    .isCheckLogin(loginCheck).build();
                            classControllers.put(controllerName, bean);
                        }
                }
            }
            return classControllers;
    }

    static Class getClassByName(String className){
        return classMap.get(className);
    }

    public static <T> List<Class> getImplClasses(Class<T> clazz, T defaultObject){
        // 判断该接口是否已经存放过
        if(interfaceImplClassMap.containsKey(clazz.getName())){
            return interfaceImplClassMap.get(clazz.getName());
        }
        // 如果缓存中没有那么直接去查找
        List<Class> tList = new ArrayList<>();
        for (Class aClass : classList) {
            if(!aClass.isInterface()&&clazz.isAssignableFrom(aClass)){
                tList.add(aClass);
            }
        }
        if(tList.isEmpty()&&defaultObject != null)tList.add(defaultObject.getClass());
        // 添加到缓存中
        interfaceImplClassMap.put(clazz.getName(), tList);
        return tList;
    }

    public static <T> Class getFirstImplClass(Class<T> clazz){
        List<Class> aClass = getImplClasses(clazz, null);
        if(aClass.size()==0){
            throw new RuntimeException(String.format("未找到[%s]的实现类",clazz.getName()));
        }
        return aClass.get(0);
    }

    public static void autowired(Object object) throws IllegalAccessException {
        Class cs = object.getClass();
        //获取该类所有的字段
        Field[] declaredFields = cs.getDeclaredFields();
        FieldAccess access = FieldAccess.get(cs);
        int i =0;
        for (Field declaredField : declaredFields) {
            boolean annotationPresent = declaredField.isAnnotationPresent(Autowired.class);
            if (annotationPresent && !scutiger.util.Field.isRrdinary(declaredField.getType())) {
                declaredField.setAccessible(true);
                Class<?> type = declaredField.getType();
                Object value;
                if(type.isInterface()){
                    Class implClass = getFirstImplClass(type);
                    Object instanceForCache = getCurrentThreadInstanceForCache(implClass);
                    if(instanceForCache != null){
                        value = instanceForCache;
                    }else{
                        value = doCreateCurrentThreadSingleBean(implClass);
                        autowired(value);
                    }
                }else{
                    Object instanceForCache = getCurrentThreadInstanceForCache(type);
                    if(instanceForCache != null){
                        value = instanceForCache;
                    }else {
                        value = doCreateCurrentThreadSingleBean(type);
                        autowired(value);
                    }

                }
                if(reflect.equals(JAVA))autowiredForReflectJava(object,declaredField,value);
                else if(reflect.equals(ASM))autowiredForReflectAsm(object,access,i,value);
            }
            i++;
        }
    }

    private static void autowiredForReflectJava(Object object,Field declaredField,Object value) throws IllegalAccessException {
        declaredField.set(object,value);
    }

    private static void autowiredForReflectAsm(Object object,FieldAccess access,Integer index,Object value){
        access.set(object, index, value);
    }

    public static void initServlet(Object object,Map<String,Integer> leiMethodMapIndex, HttpServletRequest request, HttpServletResponse response) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        if(reflect.equals(JAVA))initServletForReflectJava(object,request,response);
        else if(reflect.equals(ASM))initServletForReflectAsm(object,leiMethodMapIndex,request,response);
    }

    private static void initServletForReflectJava(Object object,HttpServletRequest request, HttpServletResponse response) throws IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        Method method1 = object.getClass().getMethod("setRequest",HttpServletRequest.class);
        invokeForForReflectJava(object,method1,new Object[]{request});
        Method method2 = object.getClass().getMethod("setResponse",HttpServletResponse.class);
        invokeForForReflectJava(object,method2,new Object[]{response});
    }

    private static void initServletForReflectAsm(Object object,Map<String,Integer> leiMethodMapIndex, HttpServletRequest request, HttpServletResponse response){
        invokeForForReflectAsm(object,"setRequest",leiMethodMapIndex,new Object[]{request});
        invokeForForReflectAsm(object,"setResponse",leiMethodMapIndex,new Object[]{response});
    }

    public static void invoke(Object object,Method method, Map<String,Integer> leiMethodMapIndex,Object[] params) throws InvocationTargetException, IllegalAccessException {
        // 取消安全检查 提高访问速度
        method.setAccessible(true);
        if(LEIClassLoader.reflect.equals(LEIClassLoader.JAVA))
            invokeForForReflectJava(object,method,params);
        else if(LEIClassLoader.reflect.equals(LEIClassLoader.ASM)){
            invokeForForReflectAsm(object,method.getName(),leiMethodMapIndex,params);
        }
    }

    private static void invokeForForReflectJava(Object object,Method method,Object[] params) throws InvocationTargetException, IllegalAccessException {
        //2.执行该类指定方法
        method.invoke(object,params);
    }

    private static void invokeForForReflectAsm(Object object,String methodName, Map<String,Integer> leiMethodMapIndex,Object[] params){
        MethodAccess access = MethodAccess.get(object.getClass());
        access.invoke(object,leiMethodMapIndex.get(methodName),params);
    }

    private static HashMap<String,Integer> parseMethodMapIndex(Class cs) {
        HashMap<String, Integer> leiMethodMapIndex = new HashMap<>();
        MethodAccess access = MethodAccess.get(cs);
        String[] methodNames = access.getMethodNames();
        for (int x = 0; x < methodNames.length; x++) {
            String methodName = methodNames[x];
            int index = access.getIndex(methodName);
            leiMethodMapIndex.put(methodName,index);
        }
        return leiMethodMapIndex;
    }

    private static HashMap<String, scutiger.impl.LMethod> parseMethodMap(Method[] methods) {
        HashMap<String, scutiger.impl.LMethod> leiMethodMap = new HashMap<>();
        for (int x = 0; x < methods.length; x++) {
            Method method = methods[x];

            Class<?>[] parameterTypes = method.getParameterTypes();
            //泛型类型参数
            Type[] genericParameterTypes = method.getGenericParameterTypes();
            LParam[] lParams = getMethodLParamsByAnnotation(method);
            String[] paramNames = new String[parameterTypes.length];
            String[] defaultValues = new String[parameterTypes.length];

            for (int i1 = 0; i1 < parameterTypes.length; i1++) {
                /*参数类型*/
                Class<?> parameterType = parameterTypes[i1];
                LParam param = lParams[i1];
                if (param != null) {
                    /*参数注解名称*/
                    String name = param.value();
                    if (StringUtils.isEmpty(name)) name = param.name();
                    paramNames[i1] = name;/*默认值*/
                    String defaultValue = param.defaultValue();
                    defaultValues[i1] = defaultValue;
                } else {
                    paramNames[i1] = null;
                    defaultValues[i1] = null;
                }
            }


            LMethod leiMethod = method.getAnnotation(LMethod.class);
            logger.info("方法 " + method.getName() + " 是否引用了注解:" +leiMethod);
            if (leiMethod != null) {
                String leiMethodName = leiMethod.value();
                if (StringUtils.isEmpty(leiMethodName)) leiMethodName = leiMethod.name();
                leiMethodName = autoAddPrefix(leiMethodName);
                scutiger.impl.LMethod leiMethod1 = scutiger.impl.LMethod.builder()
                        .paramNames(paramNames)
                        .paramTypes(parameterTypes)
                        .genericParameterTypes(genericParameterTypes)
                        .LEIMethodName(leiMethodName)
                        .methodName(method.getName())
                        .method(method)
                        .defaultValues(defaultValues).build();
                leiMethodMap.put(leiMethodName, leiMethod1);
            }
        }
        return leiMethodMap;
    }

    /**
     * 自动添加控制器的前缀 @param controller 控制器名称 @return
     */
    private static String autoAddPrefix(String controller) {
        if (controller.charAt(0) == '/') return controller;
        return '/' + controller;
    }

    /**
     * 从一个指定路径下查找所有的类 @param packagename 包名
     */
    @SuppressWarnings("rawtypes")
    private static ArrayList<Class> getAllClass(String packageName) {
        ArrayList<Class> list = new ArrayList<>();/* 返回对当前正在执行的线程对象的引用。 返回该线程的上下文 ClassLoader。*/
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        String path = packageName.replace('.', '/');
        try {
            ArrayList<File> fileList = new ArrayList<>();
            /** 这里面的路径使用的是相对路径 如果大家在测试的时候获取不到，请理清目前工程所在的路径 使用相对路径更加稳定！ 另外，路径中切不可包含空格、特殊字符等！ */
            /* getResources:查找所有给定名称的资源 获取jar包中的实现类:Enumeration<URL> enumeration = classLoader.getResources(path);*/
            Enumeration<URL> enumeration = classLoader.getResources(path);
            while (enumeration.hasMoreElements()) {
                URL url = enumeration.nextElement();/* 获取此 URL 的文件名*/
                fileList.add(new File(url.getFile()));
            }
            for (File file : fileList) list.addAll(findClass(file, packageName));
        } catch (IOException e) {
            e.printStackTrace();
        }
        return list;
    }

    /**
     * 如果file是文件夹，则递归调用findClass方法，或者文件夹下的类 如果file本身是类文件，则加入list中进行保存，并返回 @param file @param packagename @return
     */
    @SuppressWarnings("rawtypes")
    private static ArrayList<Class> findClass(File file, String packagename) {
        ArrayList<Class> list = new ArrayList<>();
        if (!file.exists()) return list;/* 返回一个抽象路径名数组，这些路径名表示此抽象路径名表示的目录中的文件。*/
        File[] files = file.listFiles();
        if (files == null) return list;
        for (File file1 : files) {
            if(YmlUtil.log)logger.info(packagename + " 路径下加载的文件:" + file1.getName());
        }
        for (File file2 : files)
            if (file2.isDirectory()) {/* assert !file2.getName().contains(".");// 添加断言用于判断*/
                if (!file2.getName().contains(".")) {
                    ArrayList<Class> arrayList = findClass(file2, packagename + "." + file2.getName());
                    list.addAll(arrayList);
                }
            } else if (file2.getName().endsWith(".class")) try {/* 保存的类文件不需要后缀.class*/
                list.add(Class.forName(packagename + '.' + file2.getName().substring(0, file2.getName().length() - 6)));
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        return list;
    }

    /**
     * 获取所有注解的LParam @param method @return
     */
    private static LParam[] getMethodLParamsByAnnotation(Method method) {
        Annotation[][] parameterAnnotations = method.getParameterAnnotations();
        if (parameterAnnotations == null || parameterAnnotations.length == 0) return new LParam[0];
        LParam[] lParams = new LParam[parameterAnnotations.length];
        int i = 0;
        // 第一个维度 方法的参数数目 (@LParam('test')String s1,String s2) 则此维度为2
        for (Annotation[] parameterAnnotation : parameterAnnotations) {
            if (parameterAnnotation.length <= 0) lParams[i++] = null;
            // 第二个维度为该参数上面的 注解数目 [[1],[0]]
            for (Annotation annotation : parameterAnnotation)
                if (annotation instanceof LParam) {
                    LParam param = (LParam) annotation;
                    lParams[i++] = param;
                }
        }
        return lParams;
    }

    /**获取当前线程的Map实例缓存中
     *
     * @param clazz class
     * @return
     */
    public static Object getCurrentThreadInstanceForCache(Class clazz){
        Map<String, Object> singleBeans = currentThreadSingleBeans.get(Thread.currentThread().getName());
        Object o = singleBeans.get(clazz.getName());
        if(o != null)return o;
        return null;
    }

    /**将创建的对象放置到当前线程的Map实例缓存中
     * @param clazz
     * @return
     */
    public static Object doCreateCurrentThreadSingleBean(Class clazz){
        Map<String, Object> singleBeans = currentThreadSingleBeans.get(Thread.currentThread().getName());
        Object newInstance = getNewInstance(clazz);
        singleBeans.put(clazz.getName(),newInstance);
        return newInstance;
    }

    /**从缓存中获取对象 没有返回null
     * @param clazz class
     * @return
     */
    public static Object getNewInstanceForCache(Class clazz){
        // 1.首先到一级缓存中查找
        String key = clazz.getName();
        if(firstConstructs.containsKey(key)){
            if(count.containsKey(key)) {
                Integer integer = count.get(key);
                count.put(key, ++integer);
            }
            return firstConstructs.get(key).newInstance();
        }
        if(secondConstructs.containsKey(key)){
            if(count.containsKey(key)) {
                Integer integer = count.get(key);
                count.put(key, ++integer);
            }
            return secondConstructs.get(key).newInstance();
        }
        return null;
    }

    /**创建一个对象并放到缓存中
     * @param clazz class
     * @return
     */
    public static Object doCreateBean(Class clazz){
        // 如果没有就创建一个实例并放置到缓存中
        String key = clazz.getName();
        count.put(key, 1);
        ConstructorAccess access = ConstructorAccess.get(clazz);
        Object object = access.newInstance();
        secondConstructs.put(key, access);
        return object;
    }

    /**获取一个实例 没有就创建一个新的实例并返回
     * @param clazz class
     * @return
     */
    public static Object getNewInstance(Class clazz){
        // 首先到缓存中查找
        Object instanceForCache = getNewInstanceForCache(clazz);
        if(instanceForCache != null)return instanceForCache;
        // 如果没有就创建一个实例并放置到缓存中
        return doCreateBean(clazz);
    }

    public static void printInfo(){
        System.out.println(runInfo());
    }

    public static String runInfo(){
        StringBuilder stringBuffer = new StringBuilder();
        stringBuffer.append("<div style='position:fixed;bottom:0;left:0;background-color:white;'>");
        stringBuffer.append("<a href='#a'>加载的class数目</a>");
        stringBuffer.append("<a href='#b'>装载的controller数量</a>");
        stringBuffer.append("<a href='#c'>其它实例缓存</a>");
        stringBuffer.append("<a href='#d'>一级缓存实例构造器数目</a>");
        stringBuffer.append("<a href='#e'>二级缓存实例构造器数目</a>");
        stringBuffer.append("<a href='#f'>类(实例)调用次数统计</a>");
        stringBuffer.append("</div>");
        stringBuffer.append("<hr><div id='a' style='background-color:#ccc;cursor:pointer;' onclick='javascript:document.getElementById(\"first\").style.display===\"block\"?document.getElementById(\"first\").style.display=\"none\":document.getElementById(\"first\").style.display=\"block\";' style='margin-top:20px;' >").append("加载的class数目:").append(classList.size()).append(" 个</div>");
        stringBuffer.append("<ul style='display:none;' id='first'>");
        for (Class aClass : classList) {
            stringBuffer.append("<li>->").append(aClass.getName()).append("</li>");
        }
        stringBuffer.append("</ul>");
        stringBuffer.append("<hr><div id='b' style='background-color:#ccc;cursor:pointer;' onclick='javascript:document.getElementById(\"second\").style.display===\"block\"?document.getElementById(\"second\").style.display=\"none\":document.getElementById(\"second\").style.display=\"block\";'>").append("装载的controller数量:").append(classControllers.size()).append(" 个</div>");
        stringBuffer.append("<ul style='display:none;'  id='second'>");
        for (String s : classControllers.keySet()) {
            stringBuffer.append("<li>").append(s).append("-->");
            LBean lBean = classControllers.get(s);
            if(lBean != null){
                for (String s1 : lBean.getLEIMethodMap().keySet()) {
                    stringBuffer.append("&nbsp;").append(s1);
                }
            }
            stringBuffer.append("</li>");
        }
        stringBuffer.append("</ul>");
        stringBuffer.append("<hr><div id='c' style='background-color:#ccc;cursor:pointer;' onclick='javascript:document.getElementById(\"three\").style.display===\"block\"?document.getElementById(\"three\").style.display=\"none\":document.getElementById(\"three\").style.display=\"block\";'>").append("接口实现类:").append(interfaceImplClassMap.size()).append(" 个</div>");
        stringBuffer.append("<ul style='display:none;'  id='three'>");
        for (String s : interfaceImplClassMap.keySet()) {
            stringBuffer.append("<li>->").append("其中(接口或类)").append(s).append("的实例 ").append(interfaceImplClassMap.get(s).size()).append(" 个").append("</li>");
        }
        stringBuffer.append("</ul>");
        stringBuffer.append("<hr><div id='d' style='background-color:#ccc;cursor:pointer;' onclick='javascript:document.getElementById(\"four\").style.display===\"block\"?document.getElementById(\"four\").style.display=\"none\":document.getElementById(\"four\").style.display=\"block\";'>").append("一级缓存实例构造器数目:").append(firstConstructs.size()).append(" 个</div>");
        stringBuffer.append("<ul style='display:none;'  id='four'>");
        for (String s : firstConstructs.keySet()) {
            stringBuffer.append("<li>").append(s).append("</li>");
        }
        stringBuffer.append("</ul>");

        stringBuffer.append("<hr><div id='e' style='background-color:#ccc;cursor:pointer;' onclick='javascript:document.getElementById(\"five\").style.display===\"block\"?document.getElementById(\"five\").style.display=\"none\":document.getElementById(\"five\").style.display=\"block\";'>").append("二级缓存实例构造器数目:").append(secondConstructs.size()).append(" 个</div>");
        stringBuffer.append("<ul style='display:none;'  id='five'>");
        for (String s : secondConstructs.keySet()) {
            stringBuffer.append("<li>").append(s).append("</li>");
        }
        stringBuffer.append("</ul>");

        stringBuffer.append("<hr><div id='f' style='background-color:#ccc;cursor:pointer;' onclick='javascript:document.getElementById(\"six\").style.display===\"block\"?document.getElementById(\"six\").style.display=\"none\":document.getElementById(\"six\").style.display=\"block\";'>").append("类(实例)调用次数统计:").append(count.size()).append(" 个</div>");
        stringBuffer.append("<ul style='display:none;'  id='six'>");
        LinkedHashMap<String, Integer> stringIntegerLinkedHashMap = sortCountMap();
        for (String s : stringIntegerLinkedHashMap.keySet()) {
            stringBuffer.append("<li>").append(s).append(" 被调用了").append(count.get(s).intValue()).append(" 次</li>");
        }
        stringBuffer.append("</ul>");

        stringBuffer.append("<hr style='margin-bottom:20px;'>");
        return stringBuffer.toString();
    }

    private static LinkedHashMap<String, Integer> sortCountMap(){
        return LEIClassLoader.count.entrySet().stream()
                .sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue,
                        (oldValue, newValue) -> oldValue, LinkedHashMap::new));
    }
}
